package indi.zhifa.engine.redis.aop;

import indi.zhifa.engine.core.common.entity.web.ServiceException;
import indi.zhifa.engine.core.common.ann.caches.CacheGet;
import indi.zhifa.engine.core.common.ann.caches.CacheParam;
import indi.zhifa.engine.redis.enums.ERedisType;
import indi.zhifa.engine.redis.properties.RedissonProperties;
import indi.zhifa.engine.redis.utils.RedisTemplateProvider;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.util.concurrent.TimeUnit;

@AllArgsConstructor
@Aspect
@Slf4j
public class CacheAop {

    final RedisTemplateProvider mRedisTemplateProvider;
    final RedissonClient mRedissonClient;
    final RedissonProperties mRedissonProperties;

    @Around("@annotation(pCacheGet)")
    public Object cacheAdvice(ProceedingJoinPoint pJoinPoint, CacheGet pCacheGet) throws Throwable {
        // 获取SpEL表达式
        String prefix = pCacheGet.prefix();
        if(StringUtils.hasText(prefix)){
            if(prefix.charAt(prefix.length()-1)!=':'){
                prefix = prefix + ":";
            }
        }
        String exp = pCacheGet.expression();

        // 创建SpEL解析器
        ExpressionParser parser = new SpelExpressionParser();

        // 准备上下文：将方法参数名和参数值绑定到EvaluationContext中
        MethodSignature signature = (MethodSignature) pJoinPoint.getSignature();
        Annotation[][] paramAnn = signature.getMethod().getParameterAnnotations();
        Object[] args = pJoinPoint.getArgs();
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramAnn.length; i++) {
            Annotation[] paramAnnotations = paramAnn[i];
            for (int j = 0; j < paramAnnotations.length; j++) {
                if(paramAnnotations[j] instanceof CacheParam cp){
                    context.setVariable(cp.name(), args[i]);
                    break;
                }
            }
        }

        // 解析表达式得到缓存键
        Expression expression = parser.parseExpression(exp);
        String cacheKey = prefix + expression.getValue(context, String.class);

        RedisTemplate<String, Object> redisTemplate = mRedisTemplateProvider.getRedisTemplate(ERedisType.CACHE);
        Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
        if(null != cacheObject){
            return cacheObject;
        }

        RLock lock = mRedissonClient.getLock(cacheKey + ":LOCK");
        try{
            if(lock.tryLock(mRedissonProperties.getWaitTime(),
                    mRedissonProperties.getLeaseTime(), TimeUnit.MILLISECONDS)){
                try{
                    cacheObject = redisTemplate.opsForValue().get(cacheKey);
                    if(null != cacheObject){
                        return cacheObject;
                    }
                    cacheObject = pJoinPoint.proceed();
                    if(null != cacheObject){
                        redisTemplate.opsForValue().set(cacheKey,cacheObject);
                    }
                }finally {
                    lock.unlock();
                }
            }
        }catch (InterruptedException e) {
            throw new ServiceException("获取锁失败",e.getMessage());
        }
        return cacheObject;
    }


}
